/**
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 * <p>
 * http://www.apache.org/licenses/LICENSE-2.0
 * <p>
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package io.gitee.huangjianbo.supos.openapi.core.http.request;

import com.alibaba.fastjson2.JSON;
import com.alibaba.fastjson2.filter.SimplePropertyPreFilter;

import io.gitee.huangjianbo.supos.openapi.core.constants.ApiClientConstant;
import io.gitee.huangjianbo.supos.openapi.core.errors.ApiClientException;
import io.gitee.huangjianbo.supos.openapi.core.http.HttpHeader;
import io.gitee.huangjianbo.supos.openapi.core.http.HttpPath;
import io.gitee.huangjianbo.supos.openapi.core.tools.ObjectUtils;
import io.gitee.huangjianbo.supos.openapi.core.tools.StringUtils;

import org.slf4j.Logger;

import java.io.File;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.Serializable;
import java.lang.reflect.Field;
import java.util.ArrayList;
import java.util.List;

/**
 * 请求参数定义接口
 * <p>
 * 此接口可定义请求头、路径参数、地址参数、请求体参数及支持multipart/form-data协议的参数。<br>
 * 其中：
 * <ul>
 *     <li>{@link PathParam}注解：用于描述路径参数</li>
 *     <li>{@link QueryParam}注解：用于描述地址参数</li>
 *     <li>{@link HeaderParam}注解：用于描述请求头</li>
 *     <li>{@link FormParam}注解：用于multipart/form-data协议的额外参数</li>
 * </ul>
 *
 * @author tomcat
 * @version 1.0
 */
public interface RequestArguments extends ArgumentsValidator, Serializable {

    /**
     * 将参数转为Query地址参数
     *
     * @return Query Param，如?a=1&b=2&c=3
     */
    default String toQueryString() {
        try {
            final List<String> queryParams = new ArrayList<>();
            for (Field field : this.getClass().getDeclaredFields()) {
                if (field.getName().equals(ApiClientConstant.SERIAL_VERSION_UID)
                        || field.getName().equals("SERIAL_VERSION_UID")
                        || field.getName().equals("interLogger")
                        || Logger.class.isAssignableFrom(field.getDeclaringClass())) {
                    continue;
                }
                if (field.isAnnotationPresent(QueryParam.class)) {
                    field.setAccessible(true);
                    Object fieldValue = field.get(this);
                    QueryParam anno = field.getAnnotation(QueryParam.class);
                    String key = StringUtils.isNotEmpty(anno.name()) ? anno.name() : field.getName();
                    if (ObjectUtils.isNull(fieldValue)) {
                        queryParams.add(key + "=");
                    } else {
                        queryParams.add(key + "=" + ObjectUtils.objectToString(fieldValue, anno.useEnumOrdinal(), anno.useMapKey()));
                    }
                }
            }
            return queryParams.isEmpty() ? "" : "?" + StringUtils.join(queryParams, "&");
        } catch (IllegalAccessException e) {
            throw new ApiClientException("Fail to convert to request parameters", e);
        }
    }

    /**
     * 将参数转换为请求头
     *
     * @return 请求头
     */
    default HttpHeader toHeader() {
        HttpHeader header = HttpHeader.of();
        try {
            for (Field field : this.getClass().getDeclaredFields()) {
                if (field.getName().equals(ApiClientConstant.SERIAL_VERSION_UID)
                        || field.getName().equals("SERIAL_VERSION_UID")
                        || field.getName().equals("interLogger")
                        || Logger.class.isAssignableFrom(field.getDeclaringClass())) {
                    continue;
                }
                if (field.isAnnotationPresent(HeaderParam.class)) {
                    field.setAccessible(true);
                    Object fieldValue = field.get(this);
                    HeaderParam anno = field.getAnnotation(HeaderParam.class);
                    String value = ObjectUtils.objectToString(fieldValue, anno.useEnumOrdinal(), anno.useMapKey());
                    header.add(StringUtils.isEmpty(anno.name()) ? field.getName() : anno.name(), value);
                }
            }
        } catch (IllegalAccessException e) {
            throw new ApiClientException("Fail to convert to header parameters", e);
        }
        return header;
    }

    /**
     * 将参数转换为Body体
     * <p>
     * {@link IgnoreParam}注解：此注解表示在将参数转换为Body体(JSON)时忽略它
     *
     * @return JSON字符串
     */
    default String toBodyJsonString() {
        SimplePropertyPreFilter filter = new SimplePropertyPreFilter();
        for (Field field : this.getClass().getDeclaredFields()) {
            if (field.getName().equals(ApiClientConstant.SERIAL_VERSION_UID)
                    || field.getName().equals("SERIAL_VERSION_UID")
                    || field.getName().equals("interLogger")
                    || Logger.class.isAssignableFrom(field.getDeclaringClass())
                    || File.class.isAssignableFrom(field.getDeclaringClass())
                    || InputStream.class.isAssignableFrom(field.getDeclaringClass())
                    || OutputStream.class.isAssignableFrom(field.getDeclaringClass())) {
                filter.getExcludes().add(field.getName());
            }
            if (field.isAnnotationPresent(QueryParam.class)
                    || field.isAnnotationPresent(IgnoreParam.class)
                    || field.isAnnotationPresent(HeaderParam.class)
                    || field.isAnnotationPresent(PathParam.class)
                    || field.isAnnotationPresent(FormParam.class)) {
                filter.getExcludes().add(field.getName());
            }
        }
        return JSON.toJSONString(this, filter);
    }

    /**
     * 将参数转换为HTTP URL的Path路径
     * <p>
     * 主要是为了填充路径参数
     *
     * @param definePath 定义的请求路径，如/user/v1/users/{userId}
     * @return 填充了相关参数的路径，如/user/v1/users/123456
     */
    default HttpPath toHttpPath(HttpPath definePath) {
        try {
            String path = definePath.toString();
            for (Field field : this.getClass().getDeclaredFields()) {
                if (field.isAnnotationPresent(PathParam.class)) {
                    field.setAccessible(true);
                    Object fieldValue = field.get(this);
                    PathParam anno = field.getAnnotation(PathParam.class);
                    String pathParamName = StringUtils.isEmpty(anno.name()) ? field.getName() : anno.name();
                    String value = ObjectUtils.objectToString(fieldValue, anno.useEnumOrdinal(), anno.useMapKey());
                    path = path.replaceAll("\\{" + pathParamName + "}", value);
                }
            }
            return HttpPath.of(path);
        } catch (IllegalAccessException e) {
            throw new ApiClientException("Fail to convert to http path: " + definePath.toString(), e);
        }
    }

    /**
     * 转换为multipart/form-data的参数，用于上传附件时需要传递额外参数
     *
     * @return form-data列表
     */
    default List<MultipartFormDataPair> toMultipartFormData() {
        try {
            final List<MultipartFormDataPair> formData = new ArrayList<>();
            for (Field field : this.getClass().getDeclaredFields()) {
                if (field.getName().equals(ApiClientConstant.SERIAL_VERSION_UID)
                        || field.getName().equals("SERIAL_VERSION_UID")
                        || field.getName().equals("interLogger")
                        || Logger.class.isAssignableFrom(field.getDeclaringClass())) {
                    continue;
                }
                if (field.isAnnotationPresent(FormParam.class)) {
                    field.setAccessible(true);
                    Object fieldValue = field.get(this);
                    FormParam anno = field.getAnnotation(FormParam.class);
                    String name = StringUtils.isEmpty(anno.name()) ? field.getName() : anno.name();
                    MultipartFormDataPair value = ObjectUtils.objectToFormDataString(name, fieldValue, anno.useEnumOrdinal());
                    if (value != null) {
                        formData.add(value);
                    }
                }
            }
            return formData;
        } catch (IllegalAccessException e) {
            throw new ApiClientException("Fail to convert to header parameters", e);
        }
    }
}
